#include <sys/types.h>
#include <stdlib.h>
#include <pthread.h>
#include <string.h>

#include "seg.h"
#include "common.h"
#include "../utils/concurrent.h"


//STCP进程使用这个函数发送sendseg_arg_t结构(包含段及其目的节点ID)给SIP进程.
//参数sip_conn是在STCP进程和SIP进程之间连接的TCP描述符.
//如果sendseg_arg_t发送成功,就返回1,否则返回-1.
int sip_sendseg(int sip_conn, int dest_nodeID, seg_t* seg)
{
  seg->header.checksum=checksum(seg);
  concurrent_printf_reminder("stcp:sip_send to node-%d!\n",dest_nodeID);
  show_seghdr(&(seg->header));

  seg_t seg_sub;
  memcpy(&seg_sub,seg,sizeof(seg_t));
  package_seg(&seg_sub);
  dest_nodeID=htonl(dest_nodeID);


  int ret=-1;
  ret=send_data(sip_conn,(const char*)&dest_nodeID,sizeof(int));
  ret = send_data(sip_conn,(const char*)&seg_sub,sizeof(seg_t));

  return ret;
}

//STCP进程使用这个函数来接收来自SIP进程的包含段及其源节点ID的sendseg_arg_t结构.
//参数sip_conn是STCP进程和SIP进程之间连接的TCP描述符.
//当接收到段时, 使用seglost()来判断该段是否应被丢弃并检查校验和.
//如果成功接收到sendseg_arg_t就返回1, 否则返回-1.
int sip_recvseg(int sip_conn, int* src_nodeID, seg_t* seg)
{
    int ret=-1;
    ret=recv_data(sip_conn,(char*)src_nodeID,sizeof(int));
    ret=recv_data(sip_conn,(char*)seg,sizeof(seg_t));
    
    *src_nodeID=ntohl(*src_nodeID);
    parse_seg(seg);

    if(ret==-1){
        return -1;
    }else if(seglost(seg)){
        concurrent_printf_warning("seg lost!\n");
        return 1;
    }else if(checkchecksum(seg)==-1){
        concurrent_printf_warning("checksum verify failed!!\n");
        return 1;
    }else{
        concurrent_printf_reminder("stcp:sip_receive from node-%d!\n",*src_nodeID);
        show_seghdr(&(seg->header));
        return 0;
    }
}

//SIP进程使用这个函数接收来自STCP进程的包含段及其目的节点ID的sendseg_arg_t结构.
//参数stcp_conn是在STCP进程和SIP进程之间连接的TCP描述符.
//如果成功接收到sendseg_arg_t就返回1, 否则返回-1.
int getsegToSend(int stcp_conn, int* dest_nodeID, seg_t* seg)
{
  int ret=-1;
  ret=recv_data(stcp_conn,(char*)dest_nodeID,sizeof(int));
  ret=recv_data(stcp_conn,(char*)seg,sizeof(seg_t));
  if(ret!=-1)
  {
    (*dest_nodeID)=ntohl(*dest_nodeID);
    parse_seg(seg);

    concurrent_printf_reminder("sip:get seg to node-%d\n",(*dest_nodeID));
    show_seghdr(&(seg->header));
  }
  return ret;
}

//SIP进程使用这个函数发送包含段及其源节点ID的sendseg_arg_t结构给STCP进程.
//参数stcp_conn是STCP进程和SIP进程之间连接的TCP描述符.
//如果sendseg_arg_t被成功发送就返回1, 否则返回-1.
int forwardsegToSTCP(int stcp_conn, int src_nodeID, seg_t* seg)
{
  concurrent_printf_reminder("sip:forward seg from node-%d\n",src_nodeID);
  show_seghdr(&(seg->header));

  seg_t seg_sub;
  memcpy(&seg_sub,seg,sizeof(seg_t));
  package_seg(&seg_sub);
  src_nodeID=htonl(src_nodeID);
  
  int ret=-1;
  ret=send_data(stcp_conn,(const char*)&src_nodeID,sizeof(int));
  ret=send_data(stcp_conn,(const char*)&seg_sub,sizeof(seg_t));
  return ret;
}

int seglost(seg_t* segPtr) {
	int random = rand()%100;
	if(random<PKT_LOSS_RATE*100) {
		//50%可能性丢失段
		if(rand()%2==0) {
            return 1;
		}
		//50%可能性是错误的校验和
		else {
			//获取数据长度
			int len = sizeof(stcp_hdr_t)+segPtr->header.length;
			//获取要反转的随机位
			int errorbit = rand()%(len*8);
			//反转该比特
			char* temp = (char*)segPtr;
			temp = temp + errorbit/8;
			*temp = *temp^(1<<(errorbit%8));
			return 0;
		}
	}
	return 0;
}

//这个函数计算指定段的校验和.
//校验和计算覆盖段首部和段数据. 你应该首先将段首部中的校验和字段清零, 
//如果数据长度为奇数, 添加一个全零的字节来计算校验和.
//校验和计算使用1的补码.
unsigned short checksum(seg_t* seg){
    seg->header.checksum=0;
    unsigned int len=seg->header.length+sizeof(seg->header);
    u_int16_t* segptr=(u_int16_t*)seg;
    u_int32_t sum=0;

    while(len>1){
        sum+=(*segptr);
        segptr++;
        len-=2;
    }
    if(len){
        char left_over[2]={0};
        left_over[0]=*((char*)segptr);
        sum+=*((u_int16_t*)(left_over));
    }

    while (sum>>16)
        sum = (sum & 0xffff) + (sum >> 16);

    return sum;
}

//这个函数检查段中的校验和, 正确时返回1, 错误时返回-1.
int checkchecksum(seg_t* seg){
    u_int16_t sum=seg->header.checksum;
    if(sum==checksum(seg)){
        seg->header.checksum=sum;
        return 1;
    }else{
        return -1;
    }
}

void package_seg(seg_t* segPtr){
    segPtr->header.src_port=htonl(segPtr->header.src_port);
    segPtr->header.dest_port=htonl(segPtr->header.dest_port);
    segPtr->header.seq_num=htonl(segPtr->header.seq_num);
    segPtr->header.ack_num=htonl(segPtr->header.ack_num);
    segPtr->header.length=htons(segPtr->header.length);
    segPtr->header.type=htons(segPtr->header.type);
    segPtr->header.rcv_win=htons(segPtr->header.rcv_win);
    segPtr->header.checksum=htons(segPtr->header.checksum);
}

void parse_seg(seg_t* segPtr){
    segPtr->header.src_port=ntohl(segPtr->header.src_port);
    segPtr->header.dest_port=ntohl(segPtr->header.dest_port);
    segPtr->header.seq_num=ntohl(segPtr->header.seq_num);
    segPtr->header.ack_num=ntohl(segPtr->header.ack_num);
    segPtr->header.length=ntohs(segPtr->header.length);
    segPtr->header.type=ntohs(segPtr->header.type);
    segPtr->header.rcv_win=ntohs(segPtr->header.rcv_win);
    segPtr->header.checksum=ntohs(segPtr->header.checksum);
}

seg_t* create_seg(unsigned int src_port,unsigned int dest_port,
                     unsigned int seq_num,unsigned int ack_num,unsigned short int type){
    seg_t* seg=(seg_t*)calloc(1,sizeof(seg_t));
    seg->header.src_port=src_port;
    seg->header.dest_port=dest_port;
    seg->header.seq_num=seq_num;
    seg->header.ack_num=ack_num;
    seg->header.type=type;
    return seg;
}

int verify_seg(seg_t* target,unsigned int src_port,unsigned int dest_port,
                     unsigned int seq_num,unsigned int ack_num,unsigned short int type){
    return target->header.src_port == src_port && target->header.dest_port == dest_port &&
           target->header.seq_num == seq_num && target->header.ack_num == ack_num && target->header.type == type;
}   

seg_t* create_syn_seg(unsigned int src_port,unsigned int dest_port){
    return create_seg(src_port,dest_port,0,0,SYN);
}

int verify_syn_seg(seg_t* target,unsigned int src_port,unsigned int dest_port){
    return verify_seg(target,src_port,dest_port,0,0,SYN);
}

seg_t* create_synack_seg(unsigned int src_port,unsigned int dest_port){
    return create_seg(src_port,dest_port,0,0,SYNACK);
}

int verify_synack_seg(seg_t* target,unsigned int src_port,unsigned int dest_port){
    return verify_seg(target,src_port,dest_port,0,0,SYNACK);
}

seg_t* create_fin_seg(unsigned int src_port,unsigned int dest_port,
                     unsigned int seq_num,unsigned int ack_num){
    return create_seg(src_port,dest_port,seq_num,ack_num,FIN);
}

int verify_fin_seg(seg_t* target,unsigned int src_port,unsigned int dest_port,
                     unsigned int seq_num,unsigned int ack_num){
    return verify_seg(target,src_port,dest_port,seq_num,ack_num,FIN);
}  

seg_t* create_finack_seg(unsigned int src_port,unsigned int dest_port,
                     unsigned int seq_num,unsigned int ack_num){

    return create_seg(src_port,dest_port,seq_num,ack_num,FINACK);
}

int verify_finack_seg(seg_t* target,unsigned int src_port,unsigned int dest_port,
                     unsigned int seq_num,unsigned int ack_num){
    return verify_seg(target,src_port,dest_port,seq_num,ack_num,FINACK);
} 

char* get_type_str(int type){
    char* ret=NULL;
    switch (type){
        case SYN:{
            ret=(char*)"SYN";
            break;
        }
        case SYNACK:{
            ret=(char*)"SYNACK";
            break;
        }
        case FIN:{
            ret=(char*)"FIN";
            break;
        }
        case FINACK:{
            ret=(char*)"FINACK";
            break;
        }
        case DATA:{
            ret=(char*)"DATA";
            break;
        }
        case DATAACK:{
            ret=(char*)"DATAACK";
            break;
        }
        default:{
            ret=(char*)"UNKNOWN";
            break;
        }
    }
    return ret;
}

void show_seghdr(stcp_hdr_t* hdr){
    concurrent_printf("********************\n");
    concurrent_printf("src_port:%d\n",hdr->src_port);
    concurrent_printf("dest_port:%d\n",hdr->dest_port);
    concurrent_printf("seq_num:%d\n",hdr->seq_num);
    concurrent_printf("ack_num:%d\n",hdr->ack_num);
    concurrent_printf("length:%d\n",hdr->length);
    concurrent_printf("type:%s\n",get_type_str(hdr->type));
    //concurrent_printf("rcv_win:%d\n",hdr->rcv_win);
    concurrent_printf("checksum:%d\n",hdr->checksum);
    concurrent_printf("********************\n");
}